/**
 *
 */
package vn.smartware.co.java.net.ftp;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;

import vn.smartware.co.java.net.commons.Const;


/**
 * JFTPClient
 * @author thuctt
 */
public class JFTPClient {

	private static Log log = LogFactory.getLog(JFTPClient.class);
	
	private String ftpAddress;
	private String ftpUserName;
	private String password;
	private String transMode;
	private boolean pasv;
	private int timeouts;
	private int tryingTimes = 3;
	private static FTPClient ftpCl = new FTPClient();
    
	/**
	 * Constructor
	 * @param ip
	 * @param user
	 * @param pass
	 * @param tranMode
	 */
	public JFTPClient(String ip, String user, String pass,
			String tranMode) {
		this.ftpAddress = ip;
		this.ftpUserName = user;
		this.password = pass;
		this.transMode = tranMode;
	}
	
	/**
	 * Constructor
	 * @param ip
	 * @param user
	 * @param pass
	 * @param tranMode
	 * @param pasv
	 * @param timeouts 
	 */
	public JFTPClient(String ip, String user, String pass,
			String tranMode, String pasv, int timeouts) {
		this.ftpAddress = ip;
		this.ftpUserName = user;
		this.password = pass;
		this.transMode = tranMode;
		if ("0".equals(pasv)){
			this.pasv = false;
		}else{
			this.pasv = true;
		}
		this.timeouts = timeouts;
	}
	
	/**
	 * Constructor
	 * @param ip
	 * @param user
	 * @param pass
	 * @param tranMode
	 * @param pasv
	 */
	public JFTPClient(String ip, String user, String pass, String tranMode, String pasv){
		this.ftpAddress = ip;
		this.ftpUserName = user;
		this.password = pass;
		this.transMode = tranMode;
		if ("0".equals(pasv)){
			this.pasv = false;
		}else{
			this.pasv = true;
		}
	}
	
	/**
	 * Change directory
	 * @param ftpDir
	 * @return true/false
	 * @throws Exception 
	 */
	public boolean cd (String ftpDir) throws Exception {
		
		boolean success = false;
        while (tryingTimes >= 0 && !success){
            try{
    		    // CWD
    	    	log.info(">> CD " + ftpDir);
    		    cwd(ftpDir);
    		    success = true;
    		    
            } catch (Exception e){
    			boolean exceptionChecked = false;
    		    //
    		    if (e.getMessage().equals(Const.ID_CODE_FTP_TRANSFER_FOLDER_FAIL)){
    		    	log.info("CD Failed.");
        		    exceptionChecked = true;
    		    }
    		    //
    		    if (exceptionChecked == false) {
    		    	log.info("Try again...");
    		    	e.printStackTrace();
    		    }
    		    tryingTimes --;
    		    if (tryingTimes < 0){
    		    	log.info("CD Failed.");
    		    	throw e;
    		    }
            }
        }
        return true;
	}
	
	/**
     * Change directory
	 * @param strDirectory 
	 * @throws Exception 
     */
    private void cwd(String strDirectory) throws Exception {

    	String command="CWD "+strDirectory;
    	System.out.println(">"+command);

    	ftpCl.changeWorkingDirectory(strDirectory);

		//
    	log.info(ftpCl.getReplyString());
    	if (!FTPReply.isPositiveCompletion(ftpCl.getReplyCode())) { 
            throw( new Exception(command + " Failed.") );
    	}
	}
	
    /**
     * upload a file to FTP
     * @param localDir
     * @param fileName
     * @return true/false
     * @throws Exception 
     */
    public boolean uploadFTP(String localDir, String fileName) throws Exception {
        
        boolean success = false;
        while (tryingTimes >= 0 && !success){
            try{
    		    // PWD 
    	        pwd();
    	        //
    	        log.info("Type Transfer: ");
    	        Transfer(transMode);
    	        
    	        //
    	        if (pasv){
    	        	pasv();
    	        }
    	        //
    	        log.info("Uploading...");
    	    	put(localDir + "/" + fileName, fileName);
    	    	
    	        // NOOP 
    	        noop();
    	        success = true;
    	        
            } catch (Exception e){
    			boolean exceptionChecked = false;
    		    //
    		    if (e.getMessage().equals(Const.ID_CODE_FTP_PUT_FILE_FAIL)){
    		    	log.info("Stored file failed.");
        		    exceptionChecked = true;
    		    }
    		    //
    		    if (e.getMessage().equals(Const.ID_CODE_FTP_TRANSFER_ASCII_MODE_FAIL) ||
    		            e.getMessage().equals(Const.ID_CODE_FTP_TRANSFER_BINARY_MODE_FAIL)){
    		    	
    		    	log.info("Transfer failed.");
        		    exceptionChecked = true;
    		    }
    		    //
    		    if (exceptionChecked == false) {
    		    	log.info("Upload failed. Try again...");
    		    	e.printStackTrace();
    		    }
    		    tryingTimes --;
    		    if (tryingTimes < 0){
    		    	log.info("Cannot upload file.");
    		    	throw e;
    		    }
    		}
        }
        return true;
    }
    
    /**
     * upload list of files to FTP
     * @param localDir
     * @param files
     * @return true/false
     * @throws Exception 
     */
    public boolean uploadFTP(String localDir, ArrayList<String> files) throws Exception {
        
        boolean success = false;
        while (tryingTimes >= 0 && !success){
            try{
    		    // PWD 
    	        pwd();
    	        //
    	        log.info("Type Transfer: ");
    	        Transfer(transMode);
    	        //
    	        if (pasv){
    	        	pasv();
    	        }
    	        //
    	        log.info("Uploading...");
    	        for (int i = 0; i < files.size(); i++) {
					String fileName = (String)files.get(i);
					put(localDir + "/" + fileName, fileName);
				}
    	        // NOOP 
    	        noop();
    	        success = true;
    	        
            } catch (Exception e){
    			boolean exceptionChecked = false;
    		    //
    		    if (e.getMessage().equals(Const.ID_CODE_FTP_PUT_FILE_FAIL)){
    		    	log.info("Stored file failed.");
        		    exceptionChecked = true;
    		    }
    		    //
    		    if (e.getMessage().equals(Const.ID_CODE_FTP_TRANSFER_ASCII_MODE_FAIL) ||
    		            e.getMessage().equals(Const.ID_CODE_FTP_TRANSFER_BINARY_MODE_FAIL)){
    		    	
    		    	log.info("Transfer failed.");
        		    exceptionChecked = true;
    		    }
    		    //
    		    if (exceptionChecked == false) {
    		    	log.info("Upload failed. Try again...");
    		    	e.printStackTrace();
    		    }
    		    tryingTimes --;
    		    if (tryingTimes < 0){
    		    	log.info("Cannot upload file.");
    		    	throw e;
    		    }
    		}
        }
        return true;
    }

    /**
     * Connect and login to FTP Server
     * @return true/false
     * @throws Exception 
     */
    public boolean connect() throws Exception {
    	
    	boolean success = false;
    	while (tryingTimes >= 0 && !success){
    		try{
    			// create connection
            	log.info("Connect starting...");
    		    login(ftpAddress, ftpUserName, password, timeouts);
    		    success = true;
    		    
    		} catch (Exception e){
    			boolean exceptionChecked = false;
    			//
    		    if (e.getMessage().equals(Const.ID_CODE_FTP_CONNECTION_FAIL) ||
    		            e.getMessage().equals(Const.ID_CODE_FTP_CONNECTION_PORT_FAIL) ||
    		            e.getMessage().equals(Const.ID_CODE_FTP_LOGIN_FAIL) || 
    		            e.getMessage().equals(Const.ID_CODE_FTP_LOGOUT_FAIL)){
    		    	
    		    	log.info("Connect failed.");
        	    	exceptionChecked = true;
    		    }
    		    //
    		    if (exceptionChecked == false) {
    		    	log.info("Connect failed. Try again...");
    		    	e.printStackTrace();
    		    }
    		    tryingTimes --;
    		    if (tryingTimes < 0){
    		    	log.error("Cannot connect.");
    		    	throw e;
    		    }
    		}
    	}
    	return true;
    }
    
    /**
     * login
     * @param ServerName
     * @param User
     * @param Pass
     * @param timeouts
     * @throws Exception
     */
    private void login(String ServerName, String User, String Pass,
			int timeouts) throws Exception {

    	if (-1 == timeouts){
    		ftpCl.setDataTimeout(9999*1000);
            ftpCl.setDefaultTimeout(9999*1000);
    	}else{
	        ftpCl.setDataTimeout(timeouts*1000);
	        ftpCl.setDefaultTimeout(timeouts*1000);
    	}
    	//
    	System.out.println(">CONNECT");
    	ftpCl.connect(ServerName);
    	System.out.println(ftpCl.getReplyString());
		//
    	if (!FTPReply.isPositiveCompletion(ftpCl.getReplyCode())) {
    		throw( new Exception("") );
    	}
		//
    	System.out.println(">USER "+User +" >PASS "+Pass);
    	ftpCl.login( User, Pass);
    	System.out.println(ftpCl.getReplyString());
		//
    	if (!FTPReply.isPositiveCompletion(ftpCl.getReplyCode())) { 
    		throw( new Exception("Login failed.") );
    	}
    }
    
    /**
     * Connect and login to FTP Server
     * @return true/false
     * @throws Exception 
     */
    public boolean disConnect() throws Exception {
    	
    	boolean success = false;
    	while (tryingTimes >= 0 && !success){
    		try{
    			//
		        logout();
		        log.info("Disconnect starting...");
    		    success = true;
    		    
    		} catch (Exception e){
    			boolean exceptionChecked = false;
    			//
    		    if (e.getMessage().equals(Const.ID_CODE_FTP_CONNECTION_FAIL) ||
    		            e.getMessage().equals(Const.ID_CODE_FTP_CONNECTION_PORT_FAIL) ||
    		            e.getMessage().equals(Const.ID_CODE_FTP_LOGIN_FAIL) || 
    		            e.getMessage().equals(Const.ID_CODE_FTP_LOGOUT_FAIL)){
    		    	
    		    	log.info("Disconnect failed.");
        	    	exceptionChecked = true;
    		    }
    		    //
    		    if (exceptionChecked == false) {
    		    	log.info("Disconnect failed. Try again...");
    		    	e.printStackTrace();
    		    }
    		    tryingTimes --;
    		    if (tryingTimes < 0){
    		    	log.error("Cannot disconnect.");
    		    	throw e;
    		    }
    		}
    	}
    	return true;
    }
    
    /** 
     * logout 
     * @throws Exception 
     */
    private void logout() throws Exception {

    	//
    	System.out.println(">QUIT");
    	ftpCl.logout();
    	System.out.println(ftpCl.getReplyString());
    	
		//
    	if (!FTPReply.isPositiveCompletion(ftpCl.getReplyCode())) { 
    		throw( new Exception("Logout failed.") );
    	}
    	ftpCl.disconnect();
    }
    
    /**
     * Download files from FTP Server
     * @param localDir
     * @param ftpFile
     * @param localFile
     * @return true/false
     * @throws Exception 
     */
    public boolean downloadFTP(String localDir, String ftpFile, String localFile)
			throws Exception {

    	if (localDir.lastIndexOf("/") != localDir.length()-1){
    		localDir += "/";
    	}
    	boolean success = false;
    	while (tryingTimes >= 0 && !success){
	        try{
	    		// PWD 
		        pwd();
		        //
		        log.info("Type transfer: ");
		        Transfer(transMode);
		        //
		        if (pasv){
		        	pasv();
		        }
		        //
		        get(ftpFile, localDir + localFile);
		        success = true;
		        
	        } catch (Exception e){
				boolean exceptionChecked = false;
			    //
			    if (e.getMessage().equals(Const.ID_CODE_FTP_GET_FILE_FAIL)){
			    	log.info("GET Failed.");
	    		    exceptionChecked = true;
			    }
			    //
			    if (e.getMessage().equals(Const.ID_CODE_FTP_TRANSFER_ASCII_MODE_FAIL) ||
			            e.getMessage().equals(Const.ID_CODE_FTP_TRANSFER_BINARY_MODE_FAIL)){
			    	log.info("Transfer failed.");
	    		    exceptionChecked = true;
			    }
			    //
			    if (exceptionChecked == false) {
			    	log.info("Download failed. Try again...");
			    	e.printStackTrace();
			    }
			    tryingTimes --;
			    if (tryingTimes < 0){
			    	log.info("Cannot download.");
			    	throw e;
			    }
			}
    	}
        return true;
    }
    
    /**
     * Download files from FTP Server
     * @param localDir
     * @param pattern
     * @return list of files downloaded from FTP Server
     * @throws Exception 
     */
    public ArrayList<String> downloadFTP(String localDir, String pattern) throws Exception {

    	ArrayList<String> files = new ArrayList<String>();
    	boolean success = false;
    	while (tryingTimes >= 0 && !success){
	        try{
	    		// PWD 
		        pwd();
		        //
		        log.info("Type transfer: ");
		        Transfer(transMode);
		        //
		        if (pasv){
		        	pasv();
		        }
		        //
	        	files = listFile(localDir, pattern);
	        	for (int i = 0; i < files.size(); i++) {
					String ele = (String)files.get(i);
					log.info("" + ele);
		        }
		        success = true;
		        
	        } catch (Exception e){
				boolean exceptionChecked = false;
			    //
			    if (e.getMessage().equals(Const.ID_CODE_FTP_PUT_FILE_FAIL)){
			    	log.info("GET Failed.");
	    		    exceptionChecked = true;
			    }
			    //
			    if (e.getMessage().equals(Const.ID_CODE_FTP_TRANSFER_ASCII_MODE_FAIL) ||
			            e.getMessage().equals(Const.ID_CODE_FTP_TRANSFER_BINARY_MODE_FAIL)){
			    	log.info("Transfer failed.");
	    		    exceptionChecked = true;
			    }
			    //
			    if (exceptionChecked == false) {
			    	log.info("Download failed. Try again...");
			    	e.printStackTrace();
			    }
			    tryingTimes --;
			    if (tryingTimes < 0){
			    	log.info("Cannot download.");
			    	throw e;
			    }
			}
    	}
        return files;
    }
        
    /**
     * PWD
     * @return boolean
     * @throws Exception 
     */
    private boolean pwd() throws Exception {

    	String command="PWD";
    	System.out.println(">"+command);
    	//
    	ftpCl.pwd();
		//
    	System.out.println(ftpCl.getReplyString());
    	if (!FTPReply.isPositiveCompletion(ftpCl.getReplyCode())) { 
    		return false;
    	}
    	return true;
    }
    
    /**
     * transfer
     * 
     * @param trnsMode 
     * @throws Exception
     */
    private void Transfer(String trnsMode) throws Exception {

        String command="";
    	if( trnsMode.equals("0") ){
    		//BINARY
            command="TYPE I";
    		System.out.println(">"+command);
    		ftpCl.setFileType(FTP.BINARY_FILE_TYPE);
            
    	}else{
    		//ASCII
        	command="TYPE A N";
        	System.out.println(">"+command);
    		ftpCl.setFileType(FTP.ASCII_FILE_TYPE);

    	}
		//
    	System.out.println(ftpCl.getReplyString());
    	if (!FTPReply.isPositiveCompletion(ftpCl.getReplyCode())) { 
            throw( new Exception(command+"") );
    	}
    }
    
    /**
     * PASV
     * @throws Exception 
     */
    private void pasv() throws Exception {
    	
        String command="PASV";
        System.out.println(">"+command);
		
        ftpCl.pasv();
        
		//
        System.out.println(ftpCl.getReplyString());
    	if (!FTPReply.isPositiveCompletion(ftpCl.getReplyCode())) { 
            throw( new Exception(command+" Failed.") );
    	}

    }

	/**
	 * get file from FTP, which have the same pattern in  
	 * the file name and save it in specify local directory
	 * 
	 * @param localDir
	 * @param pattern
	 * @return list of files just have got from FTP Server
	 * @throws Exception 
	 */
	private ArrayList<String> listFile(String localDir, String pattern) throws Exception {
	
		ArrayList<String> files = new ArrayList<String>();
		// Get an array of String objects that are the names of the files in the  
		// source directory of the remote ftp server.  
		String[] fileNames = ftpCl.listNames();
		// If fileNames is null, then an error occurred.  
		if (fileNames == null) {
			throw new Exception("FTPClient.listNames() failed. FTP Working folder not exist!!!");
		} 
		if (fileNames.length > 0) {
			for (int i = 0; i < fileNames.length; i++) {
				if (fileNames[i].toUpperCase().indexOf(pattern.toUpperCase()) != -1) {
					// Create a new local file object and a FileOutputStream
					// object that will load the data into the new local file.
					File newFile = new File(localDir + File.separator + fileNames[i]);
					FileOutputStream fos = new FileOutputStream(newFile);
					// Retrieve the source file on the remote ftp server and
					// copy it to the new local file.
					if (!ftpCl.retrieveFile(fileNames[i], fos)) {
						throw new Exception("FTPClient.retrieveFile() failed. File=" + fileNames[i]);
					}
					//
					fos.close();
					fos = null;
					files.add(localDir + File.separator + fileNames[i]);
				}
				
			}
		}
		return files;
	}

	/**
	 * get list files in specified external name from FTP Server to local
	 * 
	 * @param localDir
	 * @param ext
	 * @return list of file name
	 * @throws Exception
	 */
	public ArrayList<String> listFiles(String localDir, String ext) throws Exception {
		
		// Get an array of String objects that are the names of the files in the
		// source directory of the remote ftp server.
		String[] fileNames = ftpCl.listNames();
		// If fileNames is null, then an error occurred.  
		if (fileNames == null) {
			throw new Exception("FTPClient.listNames() failed. FTP Working folder not exist!!!");
		} 
		ArrayList<String> res = new ArrayList<String>();
		if (fileNames.length > 0) {
			for (int i = 0; i < fileNames.length; i++) {
				if (fileNames[i].toUpperCase().endsWith(ext.toUpperCase())) {
					// Create a new local file object and a FileOutputStream
					// object that will load the data into the new local file.
					File newFile = new File(localDir + File.separator + fileNames[i]);
					FileOutputStream fos = new FileOutputStream(newFile);
					// Retrieve the source file on the remote ftp server and
					// copy it to the new local file.
					if (!ftpCl.retrieveFile(fileNames[i], fos)) {
						throw new Exception("FTPClient.retrieveFile() failed. File=" + fileNames[i]);
					}
					//
					fos.close();
					fos = null;
					// Rename the source file on the remote ftp server so that it is
					// marked as previously retrieved and will not be sent again. We
					// also add the unique job pk to the prefix so that if otherfiles
					// are sent with the same name they will not get overwritten.
					String nameExcludeExt = fileNames[i].substring(0, fileNames[i].lastIndexOf("."));
					if (!ftpCl.rename(fileNames[i], nameExcludeExt + ".BAK")) {
						throw new Exception("FTPClient.rename() failed. File=" + fileNames[i]);
					}
					res.add(fileNames[i]);
				}
				
			}
		}      	
		return res;
	}

	/**
	 * move a file to target folder
	 * @param fFrom
	 * @param tarDir
	 * @throws Exception
	 */
	public void move (String fFrom, String tarDir) throws Exception {
		
		move(fFrom, tarDir, null);
	}

	/**
	 * move file to an another folder with a specified name
	 * @param fFrom
	 * @param tarDir
	 * @param fTo
	 * @throws Exception 
	 */
	private void move (String fFrom, String tarDir, String fTo) throws Exception {
		
		String tarFileName = fTo==null?fFrom:fTo;
		rename(fFrom, tarDir + File.separator + tarFileName);
	}

	/**
	 * rename a file
	 * @param fromName
	 * @param toName
	 * @throws Exception
	 */
	private void rename (String fromName, String toName) throws Exception {
		
		if (!ftpCl.rename(fromName, toName)) {
			throw new Exception("FTPClient.rename() failed. File=" + fromName);
		}  
	}

	/**
	 * Delete file
	 * @param strFileName 
	 * @throws Exception 
	 */
	public void delete(String strFileName) throws Exception {
	
		String command="DELE "+strFileName;
		System.out.println(">"+command);
	
		//
		ftpCl.deleteFile(strFileName);
	
		//
		System.out.println(ftpCl.getReplyString());
		if (!FTPReply.isPositiveCompletion(ftpCl.getReplyCode())) { 
	        throw( new Exception(command + " failed.") );
		}
	
	}

	/**
	 * get
	 * @param strFilename 
	 * @param strLocalFileName 
	 * @throws Exception 
	 */
	private void get(String strFilename,String strLocalFileName) throws Exception {
	
		String command="RETR "+strFilename;
		System.out.println(">"+command);
		
		FileOutputStream outstream = new FileOutputStream(strLocalFileName);
	
		//
		ftpCl.retrieveFile(strFilename, outstream);
	
		//
		System.out.println(ftpCl.getReplyString());
		if (!FTPReply.isPositiveCompletion(ftpCl.getReplyCode())) { 
			outstream.close();
	        throw( new Exception(command + " failed.") );
		}
	
		outstream.close();
	
	}

	/**
	 * NOOP
	 * @return boolean
	 * @throws Exception 
	 */
	private boolean noop() throws Exception {
		
	    String command="NOOP";
	    System.out.println(">"+command);
	    
	    /* NOOP */
		boolean booRet = ftpCl.sendNoOp();
		
		//
		System.out.println(ftpCl.getReplyString());
		if (!FTPReply.isPositiveCompletion(ftpCl.getReplyCode())) { 
			return false;
		}
	
		return booRet;
	    
	}

	/**
	 * PUT
	 * @param strLocalFileName 
	 * @param strFilename 
	 * @throws Exception 
	 */
	private void put(String strLocalFileName, String strFilename) throws Exception {
	
		String command="STORE "+strFilename;
		System.out.println(">"+command);
	
		FileInputStream instream = new FileInputStream(strLocalFileName);
	
		//
		ftpCl.storeFile(strFilename, instream);
		
		//
		System.out.println(ftpCl.getReplyString());
		if (!FTPReply.isPositiveCompletion(ftpCl.getReplyCode())) { 
			instream.close();
	        throw( new Exception(command+ " failed.") );
		}
	
		instream.close();
	}
	
	/**
	 * LIST
	 * @return List
	 * @throws Exception 
     */
    public List<String> ls() throws Exception {

    	String command="LIST";
        List<String> list = new ArrayList<String>();

        pasv();
        
        System.out.println(">"+command);
        String[] getList = ftpCl.listNames();

		//
        System.out.println(ftpCl.getReplyString());
    	if (!FTPReply.isPositiveCompletion(ftpCl.getReplyCode())) { 
            throw( new Exception(command + " failed.") );
    	}

        for(int i=0; i < getList.length ;i++){
        	list.add(getList[i]);
        }
        return list;
    }
}
